package edu.indiana.cs.b534.torrent.impl;

import edu.indiana.cs.b534.torrent.StorageManager;
import edu.indiana.cs.b534.torrent.TorrentException;
import edu.indiana.cs.b534.torrent.TorrentMetainfo;
import edu.indiana.cs.b534.torrent.context.TorrentContext;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.logging.Logger;

public class MemoryBasedStorageManager implements StorageManager {
    private PieceInfo[] piecesInformation;

   

    private static Logger log = Logger.getLogger(TorrentManager.TORRENT_MANAGER_NAME);

    private File storeFoler;
    private TorrentContext torrentContext;
    private int fileLength;

    public MemoryBasedStorageManager(TorrentContext torrentContext) {
        this.torrentContext = torrentContext;
    }

    /**
     * This method will help to initialize the storage for the expected number of pieces
     *
     * @param pieceInfos
     * @param pieceLength
     * @param storeFoler  - this is the folder the completed file will be saved
     * @throws TorrentException - this will be thrown when a piece is marked as completed and
     *                          the hash check for that piece failed
     */
    public void initialize(PieceInfo[] pieceInfos, File storeFoler,int fileLength) throws TorrentException {

//        // initialize all the byte buffers to hold piece data
//        for (PieceInfo pieceInfo : pieceInfos) {
//            boolean completed = pieceInfo.getValue() != null;
//
//            if(!completed){
//                pieceInfo.setValue(new byte[pieceLength]);
//            }
//            pieceInfo.setCompleted(completed);
//        }
        this.storeFoler = storeFoler;
        this.piecesInformation = pieceInfos;
       
        this.fileLength = fileLength;
    }

    public void initAsSeed(File fileToSeed, PieceInfo[] pieceInfo,int fileLength) throws TorrentException {
        byte[] bytes;
        try {
            InputStream is = new FileInputStream(fileToSeed);

            int offset = 0;
            int numRead = 0;

            for (int i = 0; i < pieceInfo.length; i++) {
            	int pieceLength = pieceInfo[i].getPieceLength();
                // Create the byte array to hold the data
                bytes = new byte[pieceLength];

                System.out.println("Reading piece number " + i);
                // Read in the bytes
                int read = 0;
                while (read < pieceLength) {
                    int lastReadcount = is.read(bytes, 0, pieceLength);
                    if (lastReadcount == -1) {
                        break;
                    } else {
                        read = read + lastReadcount;
                    }
                }

                pieceInfo[i].setValue(bytes);
                pieceInfo[i].setCompleted(true);
            }
            // Close the input stream and return bytes
            is.close();
        } catch (IOException e) {
            throw new TorrentException(e);

        }

        // loading is done now. Let's initialize using this
        initialize(pieceInfo,  null,fileLength);

    }

    public boolean[] getPieceAvailability() {

        boolean[] pieceAvailabilty = new boolean[piecesInformation.length];

        // this will keep to track whether we have at least one piece
        boolean isAtLeastOnePiecePresent = false;
        for (int i = 0; i < pieceAvailabilty.length; i++) {
            boolean isCompleted = pieceAvailabilty[i] = piecesInformation[i].isCompleted();
            if (isCompleted) {
                isAtLeastOnePiecePresent = isCompleted;
            }
        }

        return isAtLeastOnePiecePresent ? pieceAvailabilty : null;
    }

    /**
     * This will store a downloaded block in to the torrent
     *
     * @param pieceIndex   - index of the piece which this block belongs to
     * @param blockOffset
     * @param bytes        - buffered data
     * @param sourceOffset - this is the offset of which we should look in the bytes buffer passed in
     */
    public synchronized void storeBlock(int pieceIndex, int blockOffset, byte[] bytes, int sourceOffset) throws TorrentException {

        PieceInfo pieceInfo = piecesInformation[pieceIndex];
        byte[] dest = pieceInfo.getValue();
        //This void the array index errors at boundry conditions
        int amountOfDataToCopy = Math.min(bytes.length - sourceOffset, dest.length - blockOffset);
        System.arraycopy(bytes, sourceOffset, dest, blockOffset, amountOfDataToCopy);


        int blockIndex = blockOffset / Constants.BLOCK_SIZE;
        pieceInfo.setBlockAvailability(true, blockIndex);

    }

    /**
     * This will check whether the given piece is completed or not
     *
     * @param pieceIndex
     * @return
     */
    public boolean isPieceCompleted(int pieceIndex) {
        return piecesInformation[pieceIndex].isCompleted();
    }

    /**
     * will return the piece of the given index and null if the index can not be served from this
     *
     * @param pieceIndex
     * @return
     */
    public byte[] getPiece(int pieceIndex) {
        if (!(pieceIndex > piecesInformation.length)) {
            return piecesInformation[pieceIndex].getValue();
        }
        return null;
    }

    /**
     * This enables us to check whether we have completed this
     *
     * @return
     */
    public boolean isDownloadCompleted() {
        for (PieceInfo pieceInfo : piecesInformation) {
            if (!pieceInfo.isCompleted()) {
                return false;
            }
        }
        return true;
    }

    public void setDownloadCompleted() throws TorrentException {
        if (isDownloadCompleted()) {

            TorrentMetainfo torrentMetaInfo = (TorrentMetainfo) torrentContext.getProperty(TorrentContext.TORRENT_META_INFO);
            String fileName = torrentMetaInfo.getInfo().getName().getValue();

            try {
                // Create a writable file channel
                File destinationFile = new File(storeFoler, fileName);
                if(!destinationFile.isFile()){
                    destinationFile.createNewFile();
                    destinationFile.deleteOnExit();
                }
                FileChannel wChannel = new FileOutputStream(destinationFile, true).getChannel();
                int writtenCount = 0;
                for (PieceInfo pieceInfo : piecesInformation) {
                	byte[] data = pieceInfo.getValue();
                	writtenCount = writtenCount + data.length;
                    // Write the ByteBuffer contents; the bytes between the ByteBuffer's
                    // position and the limit is written to the file
                    wChannel.write(ByteBuffer.wrap(data));
                }
                // Close the file
                wChannel.close();
                if(writtenCount != fileLength){
                	throw new TorrentException("File is of length "+ fileLength + " but only "+ writtenCount + " is written ");
                }
                log.info(fileName + " saved successfully to " + storeFoler);
            } catch (IOException e) {
                throw new TorrentException("Can not save the downloaded the torrent file to " + storeFoler + File.pathSeparator + fileName + "\n Reason " + e);
            }
        } else {
            throw new TorrentException("Storage Manager is notified as completed, but some of the pieces are yet to complete");
        }
    }

    public PieceInfo getPieceInfo(int pieceIndex) {
        return this.piecesInformation[pieceIndex];
    }

    // Returns the contents of the file in a byte array.
    private static PieceInfo[] getBytesFromFile(File file, TorrentMetainfo metainfo) throws TorrentException {

        return new PieceInfo[0];
    }

    public static void main(String[] args) {
        File tempFile = new File("/home/chinthaka/Desktop/temp", "testFile.txt");
        try {
            tempFile.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();

        }
    }

	public int getFileSize() {
		return fileLength;
	}
    
    

}
